<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 * @author   howe <howe1995@163.com>
 */

namespace App\Services;

use App\Constant\ApiCodeEnum;
use App\Constant\Database\FileEnum;
use App\Model\File;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\FFMpeg;
use FFMpeg\FFProbe;
use Hyperf\Context\ApplicationContext;
use Hyperf\Filesystem\FilesystemFactory;
use Hyperf\HttpMessage\Upload\UploadedFile;

use function Hyperf\Config\config;

class FileService extends BaseService
{
    /**
     * 上传文件.
     * @param mixed $params
     */
    public function upload($params): array
    {
        // 上传的如果是头像就需要裁剪成正方头像
        $avatar = $params['avatar'];
        $file = $params['file'];
        return $this->uploadFile($file, $avatar);
    }

    /**
     * 上传文件.
     */
    public function uploadFile(UploadedFile $file, mixed $avatar): array|bool
    {
        if (! $file->isValid()) {
            $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR);
        }
        // 已存在直接返回
        $tmpPath = $file->getRealPath();
        $signature = md5_file($tmpPath);
        if ($data = $this->checkFileExists($signature)) {
            return $data;
        }
        // 生成新的文件名
        $extension = $file->getExtension();
        $newFileName = $signature . '.' . $extension;
        $newThumbnailFileName = 'thumbnail_' . $newFileName;
        // 获取文件信息
        $size = $file->getSize();
        $mimeTypeArr = explode('/', $file->getMimeType());
        $fileType = $mimeTypeArr[0] ?? 'file';
        if (! in_array($fileType, FileEnum::TYPE)) {
            $fileType = 'file';
        }
        $this->limitFileSIze($fileType, $size);
        // 初始化变量
        $width = 0;
        $height = 0;
        $duration = 0;
        $date = date('Ymd');
        $fileDir = "uploads/{$fileType}/{$date}";
        $filePath = "{$fileDir}/{$newFileName}";
        // 判断文件夹是否存在
        if (! is_dir('runtime/' . $fileDir)) {
            mkdir('runtime/' . $fileDir, 0777, true);
            chmod('runtime/' . $fileDir, 0777);
        }

        // 获取文件系统磁盘实例
        $container = ApplicationContext::getContainer();
        /** @var FilesystemFactory $filesystemFactory */
        $filesystemFactory = $container->get(FilesystemFactory::class);
        $disk = $filesystemFactory->get('local');

        // 将文件移动到指定路径
        $disk->write($filePath, file_get_contents($file->getRealPath()));

        if ($fileType == FileEnum::IMAGE) {
            // 生成缩略图
            $thumbnailFilePath = "{$fileDir}/{$newThumbnailFileName}";
            $disk->write($thumbnailFilePath, file_get_contents($file->getRealPath()));
        } elseif ($fileType == FileEnum::VIDEO) {
            $configuration = [];
            if (is_linux()) {
                $configuration = [
                    'ffmpeg.binaries' => config('static.ffmpeg.binaries'),
                    'ffprobe.binaries' => config('static.ffprobe.binaries'),
                ];
            }
            $ffmpeg = FFMpeg::create($configuration);
            $ffprobe = FFProbe::create($configuration);
            // 获取视频时长和封面图
            $video = $ffmpeg->open($tmpPath);
            $duration = (int) $ffprobe->format($tmpPath)->get('duration');
            $width = (int) $ffprobe->format($tmpPath)->get('width');
            $height = (int) $ffprobe->format($tmpPath)->get('height');
            $newThumbnailFileName = str_replace($extension, 'jpg', $newThumbnailFileName);
            $thumbnailFilePath = "{$fileDir}/{$newThumbnailFileName}";
            $video->frame(TimeCode::fromSeconds(1))->save('runtime/' . $thumbnailFilePath);
        } elseif ($fileType == FileEnum::AUDIO) {
            // 获取音频时长
        } elseif ($fileType == FileEnum::FILE) {
            $thumbnailFilePath = '';
        }

        // 将文件信息存储到数据库
        $fileRecord = new File();
        $fileRecord->name = $newFileName;
        $fileRecord->path = $filePath;
        $fileRecord->thumbnail_path = $thumbnailFilePath;
        $fileRecord->size = $size;
        $fileRecord->width = $width;
        $fileRecord->height = $height;
        $fileRecord->duration = $duration;
        $fileRecord->signature = $signature;
        $fileRecord->type = $fileType;
        $fileRecord->format = $extension;
        $fileRecord->save();
        // $fileRecord->path = $filePath;
        // $fileRecord->thumbnail_path = $thumbnailFilePath;
        // 获取文件的 URL
        $data = $fileRecord->toArray();
        $data['url'] = config('static.static_file_url') . $filePath;
        $data['thumbnail_url'] = config('static.static_file_url') . $thumbnailFilePath;

        return $data;
    }

    /**
     * 上传文件base64.
     */
    public function uploadBase64(string $base64Image): array|bool
    {
        // 解析 Base64 编码的图像数据
        [$type, $data] = explode(';', $base64Image);
        [, $data] = explode(',', $data);
        $imageData = base64_decode($data);
        $date = date('Ymd');
        // 确定文件扩展名
        $mimeType = explode(':', explode(';', $base64Image)[0])[1];
        $extension = match ($mimeType) {
            'image/jpeg' => 'jpeg',
            'image/png' => 'png',
            default => $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR),
        };
        // 生成唯一文件名
        $fileName = uniqid() . '.' . $extension;
        $fileDir = "uploads/image/{$date}";
        $filePath = "{$fileDir}/{$fileName}";

        // Storage::disk('public')->put($filePath, $imageData);
        // $fileRealPath = Storage::disk('public')->path($filePath);
        $signature = md5_file($filePath);
        if ($data = $this->checkFileExists($signature)) {
            return $data;
        }
        // 判断文件夹是否存在
        if (! is_dir('runtime/' . $fileDir)) {
            mkdir('runtime/' . $fileDir, 0777, true);
            chmod('runtime/' . $fileDir, 0777);
        }

        $filesystemFactory = di()->get(FilesystemFactory::class);
        $disk = $filesystemFactory->get('local');

        // 将文件移动到指定路径
        $disk->write($filePath, $imageData);
        $size = $disk->fileSize($filePath);
        $width = 0;
        $height = 0;
        // $size = Storage::disk('public')->size($filePath);
        // 生成缩略图
        $thumbnailFilePath = "{$fileDir}/thumbnail_{$fileName}";
        $disk->write($thumbnailFilePath, $imageData);
        // 将文件信息存储到数据库
        $fileRecord = new File();
        $fileRecord->name = $fileName;
        $fileRecord->path = $filePath;
        $fileRecord->thumbnail_path = $thumbnailFilePath;
        $fileRecord->size = $size;
        $fileRecord->width = $width;
        $fileRecord->height = $height;
        $fileRecord->duration = 0;
        $fileRecord->signature = $signature;
        $fileRecord->type = 'image';
        $fileRecord->format = $extension;
        $fileRecord->save();
        $fileRecord->path = $filePath;
        $fileRecord->thumbnail_path = $thumbnailFilePath;
        // 获取文件的 URL
        $data = $fileRecord->toArray();
        $data['url'] = config('static.static_file_url') . $filePath;
        $data['thumbnail_url'] = config('static.static_file_url') . $thumbnailFilePath;

        return $data;
    }

    /**
     * 文件是否存在.
     */
    private function checkFileExists(string $signature): array|bool
    {
        $file = File::query()->where('signature', $signature)->first();
        if ($file) {
            $data = $file->toArray();
            $data['url'] = $file->path;
            $data['thumbnail_url'] = $file->thumbnail_path;
            return $data;
        }

        return false;
    }

    /**
     * 限制上传文件大小.
     */
    private function limitFileSIze(string $fileType, int $size): void
    {
        switch ($fileType) {
            case FileEnum::IMAGE:
                if ($size > FileEnum::IMAGE_LIMIT_SIZE) {
                    $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR);
                }
                break;
            case FileEnum::VIDEO:
                if ($size > FileEnum::VIDEO_LIMIT_SIZE) {
                    $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR);
                }
                break;
            case FileEnum::AUDIO:
                if ($size > FileEnum::AUDIO_LIMIT_SIZE) {
                    $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR);
                }
                break;
            default:
                if ($size > FileEnum::FILE_LIMIT_SIZE) {
                    $this->throwBusinessException(ApiCodeEnum::CLIENT_PARAMETER_ERROR);
                }
                break;
        }
    }
}
